﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Sapphire.Linq.Mappers;

namespace Sapphire.Linq
{
    public class Mapper
    {
        private readonly IEnumerable<IObjectMapper> _mappers = InitializeMappers();
        private readonly Dictionary<string, IObjectMapper> _mappersByTypes = new Dictionary<string, IObjectMapper>();
        private static Mapper _mapper;

        private Mapper(IEnumerable<IObjectMapper> mappers)
        {
            _mappers = mappers;
        }

        public TTo Map<TTo>(object value, params object[] parameters)
        {
            if (value == null)
                return default(TTo);

            return FindMapper<TTo>(value, parameters).Map<TTo>(value, parameters);
        }

        private IObjectMapper FindMapper<TTo>(object value, object[] parameters)
        {
            var type = value.GetType().FullName + "_to_" + typeof(TTo).FullName;
            if (!_mappersByTypes.ContainsKey(type))
            {
                _mappersByTypes.Add(type, _mappers.First(m => m.IsMatch<TTo>(value, parameters)));
            }
            return _mappersByTypes[type];
        }

        /// <summary>
        /// TODO: this singleton must be thread safety
        /// </summary>
        public static Mapper Instance
        {
            get { return _mapper ?? (_mapper = new Mapper(InitializeMappers())); }
        }

        public void Clear()
        {
            _mapper = null;
        }

        private static IEnumerable<IObjectMapper> InitializeMappers()
        {
            var defaultMappers = GetMappers(typeof(IObjectMapper).Assembly);

            foreach (var mapper in defaultMappers)
            {
                yield return mapper;
            }

            var allMappers = AppDomain.CurrentDomain.GetAssemblies().ToList()
                .SelectMany(a => GetMappers(a));

            foreach (var mapper in allMappers.Except(defaultMappers))
            {
                yield return mapper;
            }
        }

        /// <summary>
        /// TODO: optimize registering of mapping. Needs more intelligence way 
        /// </summary>
        /// <param name="assembly"></param>
        /// <returns></returns>
        private static IEnumerable<IObjectMapper> GetMappers(Assembly assembly)
        {
            return assembly
                .GetTypes()
                .Where(t => typeof(IObjectMapper).IsAssignableFrom(t) && !t.IsAbstract)
                .Select(t => Activator.CreateInstance(t))
                .Cast<IObjectMapper>();
        }
    }
}